/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package okhttp3.internal.connection;

import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static okhttp3.internal.Util.closeQuietly;

import java.io.IOException;
import java.lang.ref.Reference;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownServiceException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import okhttp3.Address;
import okhttp3.Call;
import okhttp3.CertificatePinner;
import okhttp3.Connection;
import okhttp3.ConnectionPool;
import okhttp3.ConnectionSpec;
import okhttp3.EventListener;
import okhttp3.Handshake;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;
import okhttp3.internal.Internal;
import okhttp3.internal.Util;
import okhttp3.internal.Version;
import okhttp3.internal.http.HttpCodec;
import okhttp3.internal.http.HttpHeaders;
import okhttp3.internal.http1.Http1Codec;
import okhttp3.internal.http2.ErrorCode;
import okhttp3.internal.http2.Http2Codec;
import okhttp3.internal.http2.Http2Connection;
import okhttp3.internal.http2.Http2Stream;
import okhttp3.internal.platform.Platform;
import okhttp3.internal.tls.OkHostnameVerifier;
import okhttp3.internal.ws.RealWebSocket;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.Okio;
import okio.Source;

public final class RealConnection extends Http2Connection.Listener implements Connection {
	private static final String NPE_THROW_WITH_NULL = "throw with null exception";
	private static final int MAX_TUNNEL_ATTEMPTS = 21;

	private final ConnectionPool connectionPool;
	// 该对象含有了一个通过RouteSelector选中Route对象，route对象正好持有了当前请求所访问的目标主机信息（InetSocketAddress）
	private final Route route;

	// The fields below are initialized by connect() and never reassigned.

	/** The low-level TCP socket. */
	private Socket rawSocket;

	/**
	 * The application layer socket. Either an {@link SSLSocket} layered over
	 * {@link #rawSocket}, or {@link #rawSocket} itself if this connection does not
	 * use SSL.
	 */
	private Socket socket;
	private Handshake handshake;
	private Protocol protocol;
	private Http2Connection http2Connection;
	private BufferedSource source;
	private BufferedSink sink;

	// The fields below track connection state and are guarded by connectionPool.

	/**
	 * If true, no new streams can be created on this connection. Once true this is
	 * always true.
	 */
	public boolean noNewStreams;

	public int successCount;

	/**
	 * The maximum number of concurrent streams that can be carried by this
	 * connection. If {@code
	 * allocations.size() < allocationLimit} then new streams can be created on this
	 * connection.
	 */
	public int allocationLimit = 1;

	/** Current streams carried by this connection. */
	// 由此连接携带的当前流
	// 弱引用列表，每创建一个StreamAllocation，就会把它添加进该列表中，如果流关闭以后就将StreamAllocation对象从该列表中移除，正是利用这种引用计数的方式判定一个连接是否为空闲连接
	public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();

	/** Nanotime timestamp when {@code allocations.size()} reached zero. */
	public long idleAtNanos = Long.MAX_VALUE;

	public RealConnection(ConnectionPool connectionPool, Route route) {
		this.connectionPool = connectionPool;
		this.route = route;
	}

	public static RealConnection testConnection(ConnectionPool connectionPool, Route route, Socket socket,
			long idleAtNanos) {
		RealConnection result = new RealConnection(connectionPool, route);
		result.socket = socket;
		result.idleAtNanos = idleAtNanos;
		return result;
	}

	// connect-->connectSocket()进行socket连接-->Platform.get().connectSocket()-->socket.connect(address,
	// connectTimeout);(此时进行了三次握手)
	// 握手完成后调用establishProtocol（）
	// 注: 当客户端调用connect时，触发了连接请求，向服务器发送了SYN J包，这时connect进入阻塞状态；服务器监听到连接请求，即收到SYN
	// J包，调用accept函 数接收请求向客户端发送SYN K ，ACK J+1，这时accept进入阻塞状态；客户端收到服务器的SYN K ，ACK
	// J+1之后，这时connect返回，并对SYN K进行确认；服务器收到ACK K+1时，accept返回，至此三次握手完毕，连接建立。
	public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis,
			boolean connectionRetryEnabled, Call call, EventListener eventListener) {
		// 检查连接是否已经建立，若已经建立，则抛出异常，否则继续执行。连接是否建立由protocol
		// 标识，它表示在整个连接建立，及可能的协议协商过程中选择的所要使用的协议。
		if (protocol != null)
			throw new IllegalStateException("already connected");

		// 线路选择
		RouteException routeException = null;
		List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
		// 根据ConnectionSpec集合connectionSpecs构造ConnectionSpecSelector
		ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);

		if (route.address().sslSocketFactory() == null) {
			// 若请求不是安全的请求，会对请求再执行一些额外的限制。这些限制包括：
			// ConnectionSpec集合中必须要包含ConnectionSpec.CLEARTEXT。这也就是说，OkHttp的用户可以通过为OkHttpClient设置不包含ConnectionSpec.CLEARTEXT的ConnectionSpec集合来禁用所有的明文请求。
			if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
				throw new RouteException(new UnknownServiceException("CLEARTEXT communication not enabled for client"));
			}
			String host = route.address().url().host();
			if (!Platform.get().isCleartextTrafficPermitted(host)) {
				throw new RouteException(new UnknownServiceException(
						"CLEARTEXT communication to " + host + " not permitted by network security policy"));
			}
		} else {
			if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
				throw new RouteException(new UnknownServiceException("H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"));
			}
		}

		// 开始连接
		while (true) {
			try {
				// 根据请求是否需要建立隧道连接，而分别执行buildTunneledConnection() 和
				// buildConnection()。是否需要建立隧道连接的依据为
				if (route.requiresTunnel()) {
					// 建立隧道连接
					// 即对于设置了HTTP代理，且安全的连接 (SSL)
					// 需要请求代理服务器建立一个到目标HTTP服务器的隧道连接，客户端与HTTP代理建立TCP连接，以此请求HTTP代理服务在客户端与HTTP服务器之间进行数据的盲转发。
					connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
					if (rawSocket == null) {
						// We were unable to connect the tunnel but properly closed down our resources.
						// 我们无法连接隧道，但正确关闭了我们的资源
						break;
					}
				} else {
					// 建立普通连接
					connectSocket(connectTimeout, readTimeout, call, eventListener);
				}
				// 建立协议
				// 不管是建立隧道连接，还是建立普通连接，都少不了 建立协议 这一步。
				// 这一步是在建立好了TCP连接之后，而在该TCP能被拿来收发数据之前执行的。
				// 它主要为数据的加密传输做一些初始化，比如TLS握手，HTTP/2的协议协商等
				establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
				eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
				break;
				// 完成连接
			} catch (IOException e) {
				closeQuietly(socket);
				closeQuietly(rawSocket);
				socket = null;
				rawSocket = null;
				source = null;
				sink = null;
				handshake = null;
				protocol = null;
				http2Connection = null;

				eventListener.connectFailed(call, route.socketAddress(), route.proxy(), null, e);

				if (routeException == null) {
					routeException = new RouteException(e);
				} else {
					routeException.addConnectException(e);
				}

				if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
					throw routeException;
				}
			}
		}

		if (route.requiresTunnel() && rawSocket == null) {
			ProtocolException exception = new ProtocolException(
					"Too many tunnel connections attempted: " + MAX_TUNNEL_ATTEMPTS);
			throw new RouteException(exception);
		}

		if (http2Connection != null) {
			synchronized (connectionPool) {
				allocationLimit = http2Connection.maxConcurrentStreams();
			}
		}
	}

	/**
	 * Does all the work to build an HTTPS connection over a proxy tunnel. The catch
	 * here is that a proxy server can issue an auth challenge and then close the
	 * connection. 是否通过代理隧道建立HTTPS连接的所有工作。 这里的问题是代理服务器可以发出一个验证质询，然后关闭连接
	 */
	private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout, Call call,
			EventListener eventListener) throws IOException {
		// 构造一个建立隧道连接的请求
		Request tunnelRequest = createTunnelRequest();
		HttpUrl url = tunnelRequest.url();
		for (int i = 0; i < MAX_TUNNEL_ATTEMPTS; i++) {
			// 与HTTP代理服务器建立TCP连接
			connectSocket(connectTimeout, readTimeout, call, eventListener);
			// 创建隧道。这主要是将建立隧道连接的请求发送给HTTP代理服务器，并处理它的响应
			tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);

			if (tunnelRequest == null)
				break; // Tunnel successfully created.

			// The proxy decided to close the connection after an auth challenge. We need to
			// create a new
			// connection, but this time with the auth credentials.
			closeQuietly(rawSocket);
			rawSocket = null;
			sink = null;
			source = null;
			eventListener.connectEnd(call, route.socketAddress(), route.proxy(), null);
			// 重复上面的第2和第3步，直到建立好了隧道连接。至于为什么要重复多次，及关于代理认证的内容，可以参考代理协议相关的内容
			// 关于建立隧道连接更详细的过程可参考: https://www.jianshu.com/p/5c98999bc34f
		}
	}

	/**
	 * Does all the work necessary to build a full HTTP or HTTPS connection on a raw
	 * socket.
	 */
	// 完成在原始套接字上构建完整的HTTP或HTTPS连接所需的所有工作
	// 有 3 种情况需要建立非隧道连接：
	// 无代理。
	// 明文的HTTP代理。
	// SOCKS代理
	private void connectSocket(int connectTimeout, int readTimeout, Call call, EventListener eventListener)
			throws IOException {
		Proxy proxy = route.proxy();
		Address address = route.address();
		// 根据代理类型的不同处理Socket: 非SOCKS代理的情况下，通过SocketFactory创建；在SOCKS代理则传入proxy手动new一个出来
		rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
				? address.socketFactory().createSocket()
				: new Socket(proxy);

		eventListener.connectStart(call, route.socketAddress(), proxy);
		// 为Socket设置读超时
		rawSocket.setSoTimeout(readTimeout);
		try {
			// 完成特定于平台的连接建立
			Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
		} catch (ConnectException e) {
			ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
			ce.initCause(e);
			throw ce;
		}

		// The following try/catch block is a pseudo hacky way to get around a crash on
		// Android 7.0
		// More details:
		// https://github.com/square/okhttp/issues/3245
		// https://android-review.googlesource.com/#/c/271775/
		try {
			// 创建用于IO的source和sink
			source = Okio.buffer(Okio.source(rawSocket));
			sink = Okio.buffer(Okio.sink(rawSocket));
		} catch (NullPointerException npe) {
			if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
				throw new IOException(npe);
			}
		}
	}

	private void establishProtocol(ConnectionSpecSelector connectionSpecSelector, int pingIntervalMillis, Call call,
			EventListener eventListener) throws IOException {
		if (route.address().sslSocketFactory() == null) {
			if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
				socket = rawSocket;
				protocol = Protocol.H2_PRIOR_KNOWLEDGE;
				startHttp2(pingIntervalMillis);
				return;
			}

			socket = rawSocket;
			protocol = Protocol.HTTP_1_1;
			return;
		}

		eventListener.secureConnectStart(call);
		connectTls(connectionSpecSelector);
		eventListener.secureConnectEnd(call, handshake);

		if (protocol == Protocol.HTTP_2) {
			startHttp2(pingIntervalMillis);
		}
	}

	private void startHttp2(int pingIntervalMillis) throws IOException {
		socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
		http2Connection = new Http2Connection.Builder(true).socket(socket, route.address().url().host(), source, sink)
				.listener(this).pingIntervalMillis(pingIntervalMillis).build();
		http2Connection.start();
	}

	private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
		Address address = route.address();
		SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
		boolean success = false;
		SSLSocket sslSocket = null;
		try {
			// Create the wrapper over the connected socket.
			sslSocket = (SSLSocket) sslSocketFactory.createSocket(rawSocket, address.url().host(), address.url().port(),
					true /* autoClose */);

			// Configure the socket's ciphers, TLS versions, and extensions.
			ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
			if (connectionSpec.supportsTlsExtensions()) {
				Platform.get().configureTlsExtensions(sslSocket, address.url().host(), address.protocols());
			}

			// Force handshake. This can throw!
			sslSocket.startHandshake();
			// block for session establishment
			SSLSession sslSocketSession = sslSocket.getSession();
			Handshake unverifiedHandshake = Handshake.get(sslSocketSession);

			// Verify that the socket's certificates are acceptable for the target host.
			if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
				List<Certificate> peerCertificates = unverifiedHandshake.peerCertificates();
				if (!peerCertificates.isEmpty()) {
					X509Certificate cert = (X509Certificate) peerCertificates.get(0);
					throw new SSLPeerUnverifiedException(
							"Hostname " + address.url().host() + " not verified:" + "\n    certificate: "
									+ CertificatePinner.pin(cert) + "\n    DN: " + cert.getSubjectDN().getName()
									+ "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
				} else {
					throw new SSLPeerUnverifiedException(
							"Hostname " + address.url().host() + " not verified (no certificates)");
				}
			}

			// Check that the certificate pinner is satisfied by the certificates presented.
			address.certificatePinner().check(address.url().host(), unverifiedHandshake.peerCertificates());

			// Success! Save the handshake and the ALPN protocol.
			String maybeProtocol = connectionSpec.supportsTlsExtensions()
					? Platform.get().getSelectedProtocol(sslSocket)
					: null;
			socket = sslSocket;
			source = Okio.buffer(Okio.source(socket));
			sink = Okio.buffer(Okio.sink(socket));
			handshake = unverifiedHandshake;
			protocol = maybeProtocol != null ? Protocol.get(maybeProtocol) : Protocol.HTTP_1_1;
			success = true;
		} catch (AssertionError e) {
			if (Util.isAndroidGetsocknameError(e))
				throw new IOException(e);
			throw e;
		} finally {
			if (sslSocket != null) {
				Platform.get().afterHandshake(sslSocket);
			}
			if (!success) {
				closeQuietly(sslSocket);
			}
		}
	}

	/**
	 * To make an HTTPS connection over an HTTP proxy, send an unencrypted CONNECT
	 * request to create the proxy connection. This may need to be retried if the
	 * proxy requires authorization. 要通过HTTP代理建立HTTPS连接，请发送未加密的CONNECT请求以创建代理连接。
	 * 如果代理需要授权，则可能需要重试
	 */
	private Request createTunnel(int readTimeout, int writeTimeout, Request tunnelRequest, HttpUrl url)
			throws IOException {
		// Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
		// 在每个SSL +代理连接的第一个消息对上创建一个SSL隧道
		String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1";
		while (true) {
			Http1Codec tunnelConnection = new Http1Codec(null, null, source, sink);
			source.timeout().timeout(readTimeout, MILLISECONDS);
			sink.timeout().timeout(writeTimeout, MILLISECONDS);
			tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine);
			tunnelConnection.finishRequest();
			Response response = tunnelConnection.readResponseHeaders(false).request(tunnelRequest).build();
			// The response body from a CONNECT should be empty, but if it is not then we
			// should consume
			// it before proceeding.
			long contentLength = HttpHeaders.contentLength(response);
			if (contentLength == -1L) {
				contentLength = 0L;
			}
			Source body = tunnelConnection.newFixedLengthSource(contentLength);
			Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
			body.close();

			switch (response.code()) {
			case HTTP_OK:
				// Assume the server won't send a TLS ServerHello until we send a TLS
				// ClientHello. If
				// that happens, then we will have buffered bytes that are needed by the
				// SSLSocket!
				// This check is imperfect: it doesn't tell us whether a handshake will succeed,
				// just
				// that it will almost certainly fail because the proxy has sent unexpected
				// data.
				if (!source.buffer().exhausted() || !sink.buffer().exhausted()) {
					throw new IOException("TLS tunnel buffered too many bytes!");
				}
				return null;

			case HTTP_PROXY_AUTH:
				tunnelRequest = route.address().proxyAuthenticator().authenticate(route, response);
				if (tunnelRequest == null)
					throw new IOException("Failed to authenticate with proxy");

				if ("close".equalsIgnoreCase(response.header("Connection"))) {
					return tunnelRequest;
				}
				break;

			default:
				throw new IOException("Unexpected response code for CONNECT: " + response.code());
			}
		}
	}

	/**
	 * Returns a request that creates a TLS tunnel via an HTTP proxy. Everything in
	 * the tunnel request is sent unencrypted to the proxy server, so tunnels
	 * include only the minimum set of headers. This avoids sending potentially
	 * sensitive data like HTTP cookies to the proxy unencrypted.
	 *
	 * <p>
	 * In order to support preemptive authentication we pass a fake “Auth Failed”
	 * response to the authenticator. This gives the authenticator the option to
	 * customize the CONNECT request. It can decline to do so by returning null, in
	 * which case OkHttp will use it as-is
	 */
	private Request createTunnelRequest() throws IOException {
		Request proxyConnectRequest = new Request.Builder().url(route.address().url()).method("CONNECT", null)
				.header("Host", Util.hostHeader(route.address().url(), true)).header("Proxy-Connection", "Keep-Alive") // For
																														// HTTP/1.0
																														// proxies
																														// like
																														// Squid.
				.header("User-Agent", Version.userAgent()).build();

		Response fakeAuthChallengeResponse = new Response.Builder().request(proxyConnectRequest)
				.protocol(Protocol.HTTP_1_1).code(HttpURLConnection.HTTP_PROXY_AUTH).message("Preemptive Authenticate")
				.body(Util.EMPTY_RESPONSE).sentRequestAtMillis(-1L).receivedResponseAtMillis(-1L)
				.header("Proxy-Authenticate", "OkHttp-Preemptive").build();

		Request authenticatedRequest = route.address().proxyAuthenticator().authenticate(route,
				fakeAuthChallengeResponse);

		return authenticatedRequest != null ? authenticatedRequest : proxyConnectRequest;
	}

	/**
	 * 那么到底是如何判断一个RealConnection是否可以让StreamAllocation使用呢？其实一个链接能否复用的条件我们能想到无外乎地址一样、端口一样、一条链接线路的负载不超过指定负载数等等这些条件。Okhttp对复用条件做了更多的限制，详见isEligible代码
	 * Returns true if this connection can carry a stream allocation to
	 * {@code address}. If non-null {@code route} is the resolved route for a
	 * connection.
	 */
	public boolean isEligible(Address address, @Nullable Route route) {
		// If this connection is not accepting new streams, we're done.
		// 如果此链接的负载数目超过指定数目（表现为RealConnection的allocations集合的数量超过该链接指定的数量）或者noNewStreams为true时，此链接不可复用
		if (allocations.size() >= allocationLimit || noNewStreams)
			return false;

		// If the non-host fields of the address don't overlap, we're done.
		// StreamAllocation
		// 所持有的Address对象和RealConnection的Address非主机部分不同，则此链接不可复用。至于非主机部分的判定是在Address的equalsNonHost方法来体现
		if (!Internal.instance.equalsNonHost(this.route.address(), address))
			return false;

		// If the host exactly matches, we're done: this connection can carry the
		// address.
		// 在1、2判定条件都为true的话，如果两个Address对象的host或者说url中的host一样，则此链接可复用，正如注释所说，前面都满足的话，那么此时这个链接就是This
		// connection is a perfect match
		if (address.url().host().equals(this.route().address().url().host())) {
			return true; // This connection is a perfect match.
		}

		// 以上三点是Address对象的比较，如果步骤三能成功的话（地址的主机部分和非主机部分都一样），则就不需要route对象进行匹配验证了。否则则需要用route做进一步的验证来判断此链接是否可以复用。
		// 既然主机名都不相等了怎么还有复用的可能呢？由于牵扯到Http2等概念及博客篇幅问题
		// At this point we don't have a hostname match. But we still be able to carry
		// the request if
		// our connection coalescing requirements are met. See also:
		// https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
		// https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/

		// 1. This connection must be HTTP/2.
		// This connection must be HTTP/2.
		if (http2Connection == null)
			return false;

		// 2. The routes must share an IP address. This requires us to have a DNS
		// address for both
		// hosts, which only happens after route planning. We can't coalesce connections
		// that use a
		// proxy, since proxies don't tell us the origin server's IP address.
		if (route == null)
			return false;
		if (route.proxy().type() != Proxy.Type.DIRECT)
			return false;
		if (this.route.proxy().type() != Proxy.Type.DIRECT)
			return false;
		if (!this.route.socketAddress().equals(route.socketAddress()))
			return false;

		// 3. This connection's server certificate's must cover the new host.
		if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE)
			return false;
		if (!supportsUrl(address.url()))
			return false;

		// 4. Certificate pinning must match the host.
		try {
			address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
		} catch (SSLPeerUnverifiedException e) {
			return false;
		}

		// 最终可以复用
		return true; // The caller's address can be carried by this connection.
	}

	public boolean supportsUrl(HttpUrl url) {
		if (url.port() != route.address().url().port()) {
			return false; // Port mismatch.
		}

		if (!url.host().equals(route.address().url().host())) {
			// We have a host mismatch. But if the certificate matches, we're still good.
			return handshake != null && OkHostnameVerifier.INSTANCE.verify(url.host(),
					(X509Certificate) handshake.peerCertificates().get(0));
		}

		return true; // Success. The URL is supported.
	}

	public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain, StreamAllocation streamAllocation)
			throws SocketException {
		if (http2Connection != null) {
			return new Http2Codec(client, chain, streamAllocation, http2Connection);
		} else {
			socket.setSoTimeout(chain.readTimeoutMillis());
			source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
			sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
			return new Http1Codec(client, streamAllocation, source, sink);
		}
	}

	public RealWebSocket.Streams newWebSocketStreams(final StreamAllocation streamAllocation) {
		return new RealWebSocket.Streams(true, source, sink) {
			@Override
			public void close() throws IOException {
				streamAllocation.streamFinished(true, streamAllocation.codec(), -1L, null);
			}
		};
	}

	@Override
	public Route route() {
		return route;
	}

	public void cancel() {
		// Close the raw socket so we don't end up doing synchronous I/O.
		closeQuietly(rawSocket);
	}

	@Override
	public Socket socket() {
		return socket;
	}

	/** Returns true if this connection is ready to host new streams. */
	public boolean isHealthy(boolean doExtensiveChecks) {
		// 首先要可以进行IO
		if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
			return false;
		}
		// 此外对于HTTP/2，只要http2Connection存在即可
		if (http2Connection != null) {
			return http2Connection.isHealthy(System.nanoTime());
		}
		// 如果HTTP请求的method不是 "GET" ，doExtensiveChecks为true时，需要做额外的检查
		if (doExtensiveChecks) {
			try {
				int readTimeout = socket.getSoTimeout();
				try {
					socket.setSoTimeout(1);
					if (source.exhausted()) {
						return false; // Stream is exhausted; socket is closed.
					}
					return true;
				} finally {
					socket.setSoTimeout(readTimeout);
				}
			} catch (SocketTimeoutException ignored) {
				// Read timed out; socket is good.
			} catch (IOException e) {
				return false; // Couldn't read; socket is closed.
			}
		}

		return true;
	}

	/** Refuse incoming streams. */
	@Override
	public void onStream(Http2Stream stream) throws IOException {
		stream.close(ErrorCode.REFUSED_STREAM);
	}

	/** When settings are received, adjust the allocation limit. */
	@Override
	public void onSettings(Http2Connection connection) {
		synchronized (connectionPool) {
			allocationLimit = connection.maxConcurrentStreams();
		}
	}

	@Override
	public Handshake handshake() {
		return handshake;
	}

	/**
	 * Returns true if this is an HTTP/2 connection. Such connections can be used in
	 * multiple HTTP requests simultaneously.
	 */
	public boolean isMultiplexed() {
		return http2Connection != null;
	}

	@Override
	public Protocol protocol() {
		return protocol;
	}

	@Override
	public String toString() {
		return "Connection{" + route.address().url().host() + ":" + route.address().url().port() + ", proxy="
				+ route.proxy() + " hostAddress=" + route.socketAddress() + " cipherSuite="
				+ (handshake != null ? handshake.cipherSuite() : "none") + " protocol=" + protocol + '}';
	}
}
